<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace Hyperfx\Framework\Exception\Handler;

use Hyperf\Utils\Codec\Json;
use Hyperfx\Framework\Exception\AppException;
use Hyperfx\Framework\Support\Response;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\ExceptionHandler\Formatter\FormatterInterface;
use Hyperf\Grpc\StatusCode;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Psr\Http\Message\ResponseInterface;
use Throwable;

class ApiExceptionHandler extends ExceptionHandler
{
    use BaseExceptionHandler;

    const DEFAULT_HTTP_CODE = 500;

    /**
     * @var bool
     */
    private $appEnv = 'prod';

    public function __construct(protected StdoutLoggerInterface $logger, protected FormatterInterface $formatter, protected ConfigInterface $config) {
        $this->appEnv = $this->config->get("app_env", "prod");
    }

    public function handle(Throwable $throwable, ResponseInterface $response)
    {
        $code = $this->geCode($throwable);

        $logLevel = $this->getLogLevel($throwable, $code);

        $this->logger->log($logLevel, $this->formatter->format($throwable));

        return $this->transferToResponse($code, $throwable, $response);
    }

    /**
     * 处理错误码
     * 如果大小600，则三前位作为httpCode
     * @return array $httpCode, $bodyCode
     */
    private static function handleCode(int $code): array {
        if ($code == 0) {
            return [self::DEFAULT_HTTP_CODE, self::DEFAULT_HTTP_CODE];
        }

        // Grpc error code
        if ($code < 200 && $code > 0) {
            $httpCode = StatusCode::HTTP_CODE_MAPPING[$code] ?? self::DEFAULT_HTTP_CODE;
            return [$httpCode, $httpCode];
        }

        // Http Code
        if ($code < 600) {
            return [$code, $code];
        }

        $httpCode = (int) substr((string) $code, 0, 3);

        return [$httpCode, $code];
    }

    public function isValid(Throwable $throwable): bool
    {
        return true;
    }

    /**
     * Transfer the non-standard response content to a standard response object.
     */
    protected function transferToResponse(int $code, Throwable $throwable, ResponseInterface $response): ResponseInterface
    {
        [$httpCode, $bodyCode] = self::handleCode($code);

        $response = $response->withAddedHeader('Content-Type', 'application/json')->withHeader('Server', 'Jufubao');

        $message = !empty($throwable->getMessage()) ? $throwable->getMessage() : 'Internal Server Error.';

        $extend = [];
        $data = [];

        $isDebug = true;
        if ($throwable instanceof AppException) {
            $data = $throwable->getContext();
            $isDebug = $throwable->isDebug();
        }

        // 开发模式注入debug信息
        $isDebug && $this->withDebug($extend, $throwable);

        $json = Response::json($bodyCode, $message, $data, $extend);

        // 阻止异常冒泡
        $this->stopPropagation();
        return $response->withStatus($httpCode)->withBody(new SwooleStream(Json::encode($json)));
    }

    /**
     * 输出debug信息.
     */
    protected function withDebug(array &$extend, Throwable $e): void
    {
        // 仅dev时开启
        if ($this->appEnv != 'dev') {
            return;
        }
        $extend['debug'] = [
            'code' => $e->getCode(),
            'message' => $e->getMessage(),
            'file' => $e->getFile() . ':' . $e->getLine(),
            'trace' => explode("\n", $e->getTraceAsString()),
            'previous' => $e->getPrevious(),
        ];
    }
}